#include <common.h>
#include <command.h>
#include <dm.h>
#include <errno.h>
#include <spi.h>
#include <asm/gpio.h>
#include <asm/unaligned.h>
#include <bmp_layout.h>
#include <mapmem.h>
#include <malloc.h>
#include <part.h>
#include <config.h>
#include <image.h>
#include <linux/ctype.h>
#include <asm/byteorder.h>
#include <ext4fs.h>
#include <linux/stat.h>
#include <fs.h>
#include <linux/math64.h>
 
/******************************************************************************
 *函数名：nova_lcd_backlight_enable
 *参  数：void无
 *返回值：void无
 *作  用：点亮LCD背光
 ******************************************************************************/
#define backlight_en 258
#define lcd_power_en 227
void nova_lcd_backlight_enable(void) {
	run_command("gpio set H3", 0);
	run_command("gpio clean I2", 0);
}

/******************************************************************************
 *函数名：spi_gpio_init
 *参  数：void无
 *返回值：void无
 *作  用：初始化spi所用的各个gpio引脚，设为输出，并初始化值为0
 ******************************************************************************/
#define spi_dc	   69
#define spi_rst    79
#define spi_cs     263
#define spi_clk    262
#define spi_do	   264
void nova_spi_gpio_init(void) {
	//spi_dc
	run_command("gpio clean C5", 0);
	//spi_rst
	run_command("gpio clean C15", 0);
	//spi_cs
	run_command("gpio clean I7", 0);
	//spi_clk
	run_command("gpio clean I6", 0);
	//spi_do
	run_command("gpio clean I8", 0);
	/*for nova inputcard、outputcard、processcard gpio init */
	// processcard gpio is GPIO5_C3_d,the pin num in uboot is F19
	run_command("gpio set F19", 0);
	// inputcard gpio is GPIO6_A1_d,the pin num in uboot is G1
	run_command("gpio set G1", 0);
	// outputcard gpio is GPIO5_C2_d,the pin num in uboot is F18
	run_command("gpio set F18", 0);
	/*for nova fan gpio init */
	// fan1 gpio is GPIO7_A6_u,the pin num in uboot is H6
	run_command("gpio set H6", 0);
	// fan2 gpio is GPIO7_A5_d,the pin num in uboot is H5
	run_command("gpio set H5", 0);
	// fan3 gpio is GPIO5_B3_u,the pin num in uboot is F11
	run_command("gpio set F11", 0);
	
}

/******************************************************************************
 *函数名：do_spi_xfer_val
 *参  数：bus:spi2, cs:0, val:要传的值。 length：要传值的长度
 *返回值：void无
 *作  用：发送数据和命令大小的值
 ******************************************************************************/
static int do_spi_xfer_val(int bus, int cs, u8 val, int length) {
	struct spi_slave *slave;
	int ret = 0;

#ifdef CONFIG_DM_SPI
	char name[30], *str;
	struct udevice *dev;

	snprintf(name, sizeof(name), "generic_%d:%d", bus, cs);
	str = strdup(name);
	if (!str)
		return -ENOMEM;
	ret = spi_get_bus_and_cs(bus, cs, 40000000, 0, "spi_generic_drv",
				 str, &dev, &slave);
	if (ret)
		return ret;
#else
	slave = spi_setup_slave(bus, cs, 40000000, 0);
	if (!slave) {
		printf("Invalid device %d:%d\n", bus, cs);
		return -EINVAL;
	}
#endif

	ret = spi_claim_bus(slave);
	if (ret)
		goto done;
	
	ret = spi_xfer(slave, length*8, &val, NULL,
		       SPI_XFER_BEGIN | SPI_XFER_END);
#ifndef CONFIG_DM_SPI
	/* We don't get an error code in this case */
	if (ret)
		ret = -EIO;
#endif
	if (ret) {
		printf("Error %d during SPI transaction\n", ret);
	} else {
		/*nothing to do*/
	}
done:
	spi_release_bus(slave);
#ifndef CONFIG_DM_SPI
	spi_free_slave(slave);
#endif

	return ret;
}

/******************************************************************************
 *函数名：spi_send_data
 *参  数：要发送的命令或数据的内容
 *返回值：void无
 *作  用：发送命令或数据中的内容
 ******************************************************************************/
void spi_send_data(unsigned char val) {
	do_spi_xfer_val(2, 0, val, 1);
}

/******************************************************************************
 *函数名：spi_write_comd
 *参  数：要写入的命令值
 *返回值：void无
 *作  用：写命令
 ******************************************************************************/
void spi_write_comd(unsigned char val) {
	// DC引脚拉低，表示发送命令
	//spi_dc
	run_command("gpio clean C5", 0);
	// 发送接下来的8位数据
	spi_send_data(val);
}

/******************************************************************************
 *函数名：spi_write_data
 *参  数：要写入的数据值
 *返回值：void无
 *作  用：写数据
 ******************************************************************************/
void spi_write_data(unsigned char val) {
	// DC引脚置高，表示发送数据
	//spi_dc
	run_command("gpio set C5", 0);
	// 发送接下来的8位数据
	spi_send_data(val);
}

/******************************************************************************
 *函数名：lcd_set_pos
 *参  数：xs:x轴起始位置；xe:x轴结束位置；ys:y轴起始位置；ye:y轴结束位置；
 *返回值：void无
 *作  用：设定屏幕显示位置（？是否需要设置某个寄存器？）
 ******************************************************************************/
void lcd_set_pos(unsigned int xs, unsigned int xe,
											 unsigned int ys, unsigned int ye) {
	spi_write_comd(0x2A);
	spi_write_data(xs>>8);
	spi_write_data(xs&0xff);
	spi_write_data(xe>>8);
	spi_write_data(xe&0xff);
	
	spi_write_comd(0x2B);
	spi_write_data(ys>>8);
	spi_write_data(ys&0xff);
	spi_write_data(ye>>8);
	spi_write_data(ye&0xff);
	
	spi_write_comd(0x2C);
}

/******************************************************************************
 *函数名：lcd_draw_bmp_pic
 *参  数：pic:要显示的图片的地址
 *返回值：void无
 *作  用：显示图片
 ******************************************************************************/
#define ROW 		320
#define COL 		480
#define PIC_HEIGHT	480
#define PIC_WIDTH  	320
 
 void lcd_draw_bmp_pic(uchar *pic) {
	int i,j,k=0;
	lcd_set_pos(((COL-PIC_HEIGHT)/2), ((COL-PIC_HEIGHT)/2)+PIC_HEIGHT-1, 0, PIC_WIDTH-1);
	//spi_dc
	run_command("gpio set C5", 0);
	//printf("lcd_draw_bmp_pic display\n");
	for (i=0; i<PIC_WIDTH; i++) {
		for (j=0; j<PIC_HEIGHT; j++) {
			do_spi_xfer_val(2, 0, (pic[k+2]&0xfc), 1);
			do_spi_xfer_val(2, 0, (pic[k+1]&0xfc), 1);
			do_spi_xfer_val(2, 0, (pic[k+0]&0xfc), 1);
			k += 3;
		}
	}
}

/******************************************************************************
 *函数名：spi_lcd_ili9488_init
 *参  数：void无
 *返回值：void无
 *作  用：lcd驱动IC的初始化配置
 ******************************************************************************/
void spi_lcd_ili9488_init(void) {
	// 置高重启引脚，并延时
	//spi_rst
	run_command("gpio set C15", 0); mdelay(1);//可以选择msleep（1）；
	// 拉低重启引脚，并延时
	run_command("gpio clean C15", 0); mdelay(120);
	// 置高重启引脚，并延时
	run_command("gpio set C15", 0); mdelay(120);
	// 配置ili9488
		//Power Control 1
		spi_write_comd(0xC0);
		spi_write_data(0x0f);	// VREG1OUT = +5.0v
		spi_write_data(0x0c);	// VREG2OUT = -5.0v
		//Power Control 2
		spi_write_comd(0xC1);
		spi_write_data(0x41);	// VGH x 5;VGL x 4;(default)
		//VCOM Control(VCOM是液晶分子偏转的参考电压)
		spi_write_comd(0xC5);
		spi_write_data(0x00);	// nVM = 0:NV memory is not programmed
		spi_write_data(0x3A);	// VCM_REG [7:0] = 0x3A; generate VCOM voltage = -1.09375
		spi_write_data(0x80);	// VCM_REG_EN = 1:VCOM value from VCM_REG [7:0]
		//Memory Access Control(设置图像正反左右对角等)
		spi_write_comd(0x36);
		spi_write_data(0x28);	// MY=1, MX = 1, MV = 1,BGR = 1;
		//Interface Pixel Format
		spi_write_comd(0x3A);
		spi_write_data(0x56);	// 18 bits/pixel
		//Frame Rate Control
		spi_write_comd(0xB1);
		spi_write_data(0xA0);	// Division Ratio = Fosc;Frame rate = 60HZ
		//spi_write_data(0x11);
		//Display Inversion Control
		spi_write_comd(0xB4);
		spi_write_data(0x02);	// Inversion = 2 dot inversion;
		//Set Image Function
		spi_write_comd(0xE9);
		spi_write_data(0x00); 	// default
		//Interface Mode Control
		spi_write_comd(0xB0);
		spi_write_data(0x00); 	// default
		//Adjust Control 3
		spi_write_comd(0xF7);
		spi_write_data(0xA9); 	// default
		spi_write_data(0x51); 	// default
		spi_write_data(0x2C); 	// default
		spi_write_data(0x82); 	// default
		//Positive Gamma Control
		spi_write_comd(0xE0);
		spi_write_data(0x00);	// Set the gray scale voltage to adjust the gamma characteristics of the TFT panel
		spi_write_data(0x10);	// ...
		spi_write_data(0x14);
		spi_write_data(0x03);
		spi_write_data(0x0E);
		spi_write_data(0x04);
		spi_write_data(0x36);
		spi_write_data(0x56);
		spi_write_data(0x4B);
		spi_write_data(0x04);
		spi_write_data(0x0C);
		spi_write_data(0x0A);
		spi_write_data(0x30);
		spi_write_data(0x34);
		spi_write_data(0x0F);
		//Negative Gamma Control
		spi_write_comd(0xE1);	
		spi_write_data(0x00);	// Set the gray scale voltage to adjust the gamma characteristics of the TFT panel.
		spi_write_data(0x0E);	// ...
		spi_write_data(0x13);
		spi_write_data(0x03);
		spi_write_data(0x10);
		spi_write_data(0x06);
		spi_write_data(0x3E);
		spi_write_data(0x34);
		spi_write_data(0x55);
		spi_write_data(0x05);
		spi_write_data(0x0F);
		spi_write_data(0x0E);
		spi_write_data(0x3A);
		spi_write_data(0x3E);
		spi_write_data(0x0F);
		//Display Function Control
		spi_write_comd(0xB6);
		spi_write_data(0x00);	// 30 set rgb
		spi_write_data(0x22);	// GS,SS 02，42，62

		spi_write_comd(0x21);

		spi_write_comd(0x11);
		mdelay(120);
}

/******************************************************************************
 *函数名：lcd_partial_set
 *参  数：sr:起始行， er:终止行
 *返回值：void无
 *作  用：设置屏幕的部分模式
 ******************************************************************************/
void lcd_partial_set(unsigned int sr, unsigned int er) {
	spi_write_comd(0x30);
	spi_write_data(sr>>8);
	spi_write_data(sr&0xff);
	spi_write_data(er>>8);
	spi_write_data(er&0xff);
	spi_write_comd(0x12);
}

/******************************************************************************
 *函数名：lcd_get_bmp
 *参  数：ulong addr:内存中存放图片的地址
 *返回值：返回bmp图片的数据的地址
 *作  用：去掉bmp的头信息，过去图片数据
 ******************************************************************************/
uchar *lcd_get_bmp(ulong addr) {
	uchar *bmap;
	struct bmp_image *bmp = map_sysmem(addr, 0);
	addr = map_to_sysmem(bmp);
	bmp = (struct bmp_image *)map_sysmem(addr, 0);
	bmap = (uchar *)bmp + get_unaligned_le32(&bmp->header.data_offset);
	//printf("lcd_get_bmp is ok!\n");
	return bmap;
}

int logo_load_from_ext4(char *dev_name, char *dev_part, char *dst_addr, char *file_path) {
	unsigned long addr;
	const char *addr_str;
	const char *filename;
	loff_t bytes;
	loff_t pos;
	loff_t len_read;
	int ret;
	unsigned long time;
	char *ep;

	if (fs_set_blk_dev(dev_name, dev_part, FS_TYPE_EXT))
		return 1;

	if (5 >= 4) {
		addr = simple_strtoul(dst_addr, &ep, 16);
		if (ep == dst_addr || *ep != '\0')
			return CMD_RET_USAGE;
	} else {
		addr_str = getenv("loadaddr");
		if (addr_str != NULL)
			addr = simple_strtoul(addr_str, NULL, 16);
		else
			addr = CONFIG_SYS_LOAD_ADDR;
	}

	filename = file_path;
	
	bytes = 0;

	pos = 0;

	time = get_timer(0);
	ret = fs_read(filename, addr, pos, bytes, &len_read);
	time = get_timer(time);
	if (ret < 0)
		return 1;

	printf("%llu bytes read in %lu ms", len_read, time);
	if (time > 0) {
		puts(" (");
		print_size(div_u64(len_read, time) * 1000, "/s");
		puts(")");
	}
	puts("\n");

	setenv_hex("fileaddr", addr);
	setenv_hex("filesize", len_read);

	return 0;
}


/*###################################主函数###################################
 #函数名：lcd_get_bmp
 #参  数：ulong addr:内存中存放图片的地址
 #返回值：返回bmp图片的数据的地址
 #作  用：去掉bmp的头信息，过去图片数据
 #############################################################################*/
int nova_lcd_logo(void) {
	char *s = NULL;
	nova_lcd_backlight_enable();
	nova_spi_gpio_init();
	spi_lcd_ili9488_init();
	//printf("nova_lcd_logo display\n");
	
	// ~~/include/configs/firefly-rk3288.h
	s = getenv("logocmd");
	if(s) {
	     if (run_command(s, 0)) {
		    printf("cmd=(%s) run error!!\n", s);
			logo_load_from_ext4("mmc", "0:7", "0x04000000", "/usr/uhd100/logo/logo.bmp");
			printf("logo load by logo_load_from_ext4!!\n");
			return -1;
		 }
	} else {
	    printf("logocmd is Null\n");
		return -2;
	}
	
	lcd_draw_bmp_pic(lcd_get_bmp(0x04000000));

	spi_write_comd(0x29); //display on
	
	return 0;
}


